dash(coinbase): include masternode payee output in stratum-served coinbase (fix bad-cb-payee won-block reject)#746
Closed
frstrtr wants to merge 1 commit into
Closed
Conversation
…-cb-payee won-block reject) Root cause (hex-confirmed live against testnet dashd 2026-07-19, GBT proposal mode): compute_dash_payouts silently 'continue'd on any GBT payment whose payee string failed decode_payee_script, and normalize_payment discarded dashd's GBT-provided raw scriptPubKey bytes whenever a non-empty base58 payee was present -- so there was no byte-faithful fallback. The built coinbase then LACKED the masternode payee output (a consensus-REQUIRED output), and dashd rejected every won block with bad-cb-payee: silent, deterministic reward loss. The deterministic field trigger: a net-mode/daemon-chain mismatch (mainnet-mode binary against a testnet dashd applies address_version=76 to a 'y...' testnet payee -> base58 decode fails -> output dropped). NodeRPC::check() previously passed that mismatch silently. Falsified alternates (evidence): the fresh-template stratum coinbase on the right net is dashd-valid (proposal verdict null, twice, and a live won block ACCEPTED on testnet -- height 1517418, 0000007a8b36efe807cbc0ea37172d3267b93978a375c1c247045bb009efaa5b, with the masternode payee + platform burn + CbTx payload on-chain); template + coinbase + tx set are frozen as ONE atomic snapshot per job, so intra-height GBT drift cannot desync payee vs body; tip-move staleness produces prev-based rejects (observed live: bad-chainlock), never bad-cb-payee. Fix (fenced to src/impl/dash/ + its tests): * coin/rpc_data.hpp normalize_payment: ALWAYS preserve the raw GBT scriptPubKey bytes in PackedPayment.script alongside the payee string (payee-string semantics unchanged -- share-wire compatible; the sharechain PackedPayment is a distinct struct). * coinbase_builder.hpp compute_dash_payouts: (a) payee string decode as before; (b) on decode failure fall back to the GBT script bytes VERBATIM with a LOUD warning; (c) if neither is usable, THROW -- refuse to build a doomed coinbase (stratum serves no job and screams) instead of silently dropping a consensus-required output. * coin/rpc.cpp NodeRPC::check(): chain-identity guard -- mainnet mode requires chain=='main'; --testnet rejects chain=='main' (accepts test/regtest/devnet for the tuned-net harness posture). Wrong-net now fails LOUDLY at connect instead of mining doomed work. * coin/embedded_gbt.hpp: embedded arm also carries the raw scriptPayout bytes for the same byte-faithful fallback. KATs (test_dash_cb_payee, existing allowlisted target; + test_dash_coinbase_parity updated to the never-drop rule): * normalize_payment preserves the raw script alongside a base58 payee; * right-net coinbase contains burn + masternode payee (exact GBT script+amount, real captured testnet GBT entries); * wrong-net params fall back to the GBT script verbatim (the pre-fix regression case: masternode output vanished); * undecodable payee without script bytes throws (never drops); * GOLDEN: byte-for-byte full coinbase for the real height-1517409 testnet template, the exact serialization dashd accepted live via getblocktemplate proposal mode. Verification: test_dash_cb_payee 12/12, test_dash_coinbase_parity 11/11, test_dash_stratum_work_source 19/19, g3_assembled/block_producer/ embedded_gbt/coinbase_muldiv/donation_combined/get_work/work_source/ node_coin_state/rpc_request all green; --selftest OK; live testnet: stratum-served coinbase proposal-accepted, won block submitblock ACCEPTED, tip advanced with our hash.
This was referenced Jul 19, 2026
frstrtr
added a commit
that referenced
this pull request
Jul 19, 2026
…live single-shot fetch (#748) The Web-static verify gate fetched Chrome-for-Testing from Google's CDN on every self-hosted run via a single @puppeteer/browsers install. A transient outbound blip (ENETUNREACH/ETIMEDOUT, run 29675949544) false-red the whole gate on PRs that never touch web/ (e.g. dash-fenced #746/#747). Reuse an already-unpacked Chrome under ~/.cache/puppeteer (warm path = no network) and bound-retry the cold fetch (3x, backoff). Co-authored-by: ci-steward <frstrtr@users.noreply.github.com>
Owner
Author
|
Closing as superseded. The DASH won-block bad-cb-payee reward-safety issue was fixed and shipped via #751 (bind job to GBT snapshot + reconnect invalidation + submit-time payee guard), merged to master and released as v0.2.3.2 — and now proven in production (block 2507753 accepted by dashd with the correct masternode payee). This earlier approach is no longer needed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release-blocking bug: won DASH blocks rejected
bad-cb-payeec2pool-dash could build a coinbase that dashd rejects with
bad-cb-payeewhen a block is found — the won block (and its reward) was silently lost. This gates the DASH release.Hex-confirmed root cause (live testnet dashd, GBT proposal mode, 2026-07-19)
compute_dash_payoutssilently dropped any GBT-mandated payment (masternode / superblock / platform burn) whose payee string faileddecode_payee_script(if (pm_script.empty()) continue;), andnormalize_paymentdiscarded dashd's GBT-provided raw"script"scriptPubKey bytes whenever a non-empty base58 payee was present — so there was no byte-faithful fallback. The coinbase then lacked a consensus-REQUIRED output and dashd rejected every won block withbad-cb-payee.Deterministic field trigger: net-mode/daemon-chain mismatch. A mainnet-mode binary against a testnet dashd applies
address_version=76to they...testnet masternode payee → base58 decode fails → the masternode output vanishes.NodeRPC::check()passed this mismatch silently (it only probed the mainnet-genesis direction).Evidence (same daemon, same height-1517409 template, byte diff):
6a(66966830) ‖ MN payee76a91464f2b2b84f62d68a2cd7f7f5fb2b5aa75ef716d788ac(111611384) ‖ donation ‖ OP_RETURN ‖ CbTx → dashd proposal verdictnull(valid)bad-cb-payeeFalsified alternates (per the parallel investigation): the fresh-template stratum coinbase on the right net is dashd-valid (proposal-accepted twice + a live won block ACCEPTED, below); template+coinbase+txs are frozen as ONE atomic
DashWorkDatasnapshot per job, so intra-height GBT drift cannot desync payee vs body; tip-move staleness produces prev-based rejects (observed live:bad-chainlock), neverbad-cb-payee.Fix (fenced to
src/impl/dash/+ tests)coin/rpc_data.hpp—PackedPaymentgains ascriptfield;normalize_paymentALWAYS preserves the raw GBT scriptPubKey bytes alongside the payee string (payee-string semantics unchanged → share-wire compatible; the sharechainPackedPaymentis a distinct struct).coinbase_builder.hpp—compute_dash_payoutsnever drops: (a) decode the payee string as before; (b) on failure, emit the GBT script bytes verbatim with a LOUD warning; (c) if neither is usable, throw — the stratum path serves no job and screams, instead of letting a miner burn hashrate on a block dashd must reject.coin/rpc.cpp— chain-identity guard incheck(): mainnet mode requireschain=="main";--testnetrejectschain=="main"(accepts test/regtest/devnet for the tuned-net harness posture). Wrong-net now fails loudly at connect.coin/embedded_gbt.hpp— embedded arm also carries rawscriptPayoutbytes for the same fallback.KATs (existing allowlisted targets — no new test target, no build.yml change)
test_dash_cb_payee(12/12): raw-script preservation; right-net coinbase contains burn + MN payee with exact GBT script+amount (real captured testnet GBT entries); wrong-net falls back to GBT script verbatim (the pre-fix vanishing-output case); undecodable-without-script throws; GOLDEN — byte-for-byte full coinbase for the real height-1517409 template, the exact serialization dashd accepted live via proposal mode.test_dash_coinbase_parity(11/11): updated to the never-drop rule + newUndecodablePayeeThrowsNeverDrops.Verification
test_dash_cb_payee12/12,test_dash_coinbase_parity11/11,test_dash_stratum_work_source19/19,g3_assembled9/9,block_producer9/9,embedded_gbt9/9,coinbase_muldiv,donation_combined,get_work,work_source,node_coin_state,rpc_requestall green;--selftestOK.null).Chain mismatch: dashd reports chain='test' but c2pool-dash is in MAINNET mode...submitblockACCEPTED, tip advanced to height 1517418 with our hash0000007a8b36efe807cbc0ea37172d3267b93978a375c1c247045bb009efaa5b; on-chain coinbase carries the platform burn (0.66966830), the masternode payeeyVXDAM73Tg6A44Bm3qduXsMCYxzuqBCT48(1.11611384), donation, OP_RETURN ref slot, and the full CbTx payload. (Two prior wins during the run were rejectedbad-chainlock— natural stale-tip races while CPU-mining, not payee failures.)